OpenAI Realtime 세션 시작 실패 원인 분석
결론
The string did not match the expected pattern.는 OpenAI Realtime API가 직접 반환한 에러 메시지라기보다,
브라우저 WebRTC 엔진이 OpenAI Realtime 서버에서 받은 SDP answer 문자열을
RTCPeerConnection.setRemoteDescription()에서 파싱하다 실패한 런타임 에러일 가능성이 가장 높다.
현재 로그만으로는 instruction 문자열 문제라고 판단할 근거가 낮다. 로그에는 activity instruction 본문,
OpenAI API 400/500 응답, Call creation failed, Invalid SDP response 같은 서버/API 실패 흔적이 없다.
전체 로그 흐름
| 시간 | 레벨 | 로그 | 해석 |
|---|---|---|---|
| 08:58:57 - 09:02:25 | INFO | Entry request failed, continuing in standalone modeRoomNotFound |
게스트 방 입장 요청이 실패했지만, 코드상 standalone mode로 계속 진행한다. 직접적인 AI 세션 실패 원인은 아니다. |
| 09:08:04 - 09:19:42 | WARN | setSinkId failed: DOMException: A user gesture is required |
사용자 제스처 없이 오디오 출력 장치를 지정하려다 브라우저가 거부했다. catch 후 경고만 남기므로 세션 시작을 중단시키지는 않는다. |
| 09:20:36.242 | ERROR | [AI_SESSION] Failed to start sessionThe string did not match the expected pattern.activityId: activity-1779091460856-4 |
AI 세션 시작 중 실제 실패가 발생했다. activityId는 실패한 activity 식별자이며, 그 자체가 instruction 문제를 의미하지 않는다. |
| 09:20:36.243 | ERROR | [AI_SESSION] Failed to start session (handler) |
내부 startSession 에러가 handler에서 재로깅된 것이다. 원인은 직전 에러와 동일하다. |
관련 코드 경로
세션 시작 흐름은 다음과 같다.
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const answer = await createSession(
offer.sdp,
activity,
voice || avatar?.voice || DEFAULT_VOICE,
user,
avatar,
subAvatars,
realtimeModelVersion?.current,
signal,
turnDetectionRef.current.silence_duration_ms,
);
await pc.setRemoteDescription(answer);
answer.sdp는 우리 서버가 생성한 값이 아니라, 우리 서버가 OpenAI Realtime API
https://api.openai.com/v1/realtime/calls에 offer SDP를 전달한 뒤 text로 받은 OpenAI 서버의 SDP answer다.
formData.set("sdp", sdp);
formData.set("session", JSON.stringify(sessionConfig));
const res = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: formData,
});
const answerSdp = await res.text();
instruction 문제 가능성
낮음.
instruction 문제가 직접 원인이라면 보통 다음과 같은 흔적이 있어야 한다.
/api/realtime/call에서 400/500 응답Failed to create call또는Call creation failed- OpenAI API 응답 본문에 validation error
Invalid SDP response
제공된 로그에는 위 흔적이 없다. 또한 instruction 치환 코드는 정규식 기반 문자열 처리이며, 해당 메시지를 직접 생성하는 경로가 확인되지 않았다.
setSinkId 로그와의 관계
직접 관련은 낮음.
DOMException: A user gesture is required는 audioEl.setSinkId(audioOutputDeviceId)에서 발생한다.
이 호출은 try/catch로 감싸져 있고 실패해도 경고만 남긴 뒤 세션 시작 흐름은 계속 진행된다.
try {
await audioEl.setSinkId(audioOutputDeviceId);
} catch (e) {
console.warn("setSinkId failed:", e);
}
따라서 이 경고는 원하는 출력 장치로 소리가 나지 않을 수 있다는 신호이지,
The string did not match the expected pattern. 세션 실패의 직접 원인으로 보기는 어렵다.
네트워크 문제 가능성
직접 가능성은 낮고, 간접 가능성은 제한적으로 있음.
일반적인 네트워크 문제라면 보통 다음 에러가 나온다.
Failed to fetchNetworkErrorAbortErrorSession creation timeout after 10sICE connection failed
현재 에러는 이미 받은 문자열을 브라우저가 특정 패턴으로 파싱하다 실패하는 형태다. 다만 프록시/서버 문제로 SDP 응답이 변형되거나 잘못 전달된다면 간접적으로 비슷한 현상이 날 수 있다.
가장 가능성 높은 원인
-
OpenAI Realtime SDP answer와 현재 브라우저 WebRTC 파서의 호환성 문제
특히 Safari/WebKit 계열이면 SDP 파싱이 더 엄격할 수 있다. -
SDP 문자열 형식 문제
줄바꿈, 지원하지 않는 SDP attribute, codec/header extension 등으로setRemoteDescription()에서 실패했을 수 있다. -
응답 객체 검증 부족
answer.type,answer.sdp, SDP 앞부분/길이를 실패 직전에 기록하지 않아 현재 로그만으로 확정이 어렵다.
권장 대처
1. 실패 지점 로그 보강
try {
await pc.setRemoteDescription(answer);
} catch (error) {
logger.error("Failed to apply realtime answer", {
errorName: error instanceof Error ? error.name : undefined,
errorMessage: error instanceof Error ? error.message : String(error),
errorStack: error instanceof Error ? error.stack : undefined,
answerType: answer?.type,
sdpStartsWithV: answer?.sdp?.startsWith("v="),
sdpLength: answer?.sdp?.length,
sdpHead: answer?.sdp?.slice(0, 300),
activityId: activity.id,
});
throw error;
}
2. setRemoteDescription 전 answer 검증
if (
answer?.type !== "answer" ||
typeof answer.sdp !== "string" ||
!answer.sdp.startsWith("v=")
) {
throw new Error("Invalid realtime answer SDP");
}
3. SDP 줄바꿈 정규화
const normalizedAnswer = {
type: "answer" as const,
sdp: answer.sdp.replace(/\r?\n/g, "\r\n"),
};
await pc.setRemoteDescription(normalizedAnswer);
4. 1회 재시도
setRemoteDescription 실패 시 기존 RTCPeerConnection을 닫고,
offer 생성부터 OpenAI Realtime call 생성까지 한 번만 재시도한다.
같은 SDP answer를 재사용하지 않는 것이 중요하다.
5. Safari/WebKit 대응
- Safari에서만 재현되는지 user agent와 함께 로깅한다.
- Safari에서 실패 시 Chrome/Edge 사용 안내 또는 fallback UX를 제공한다.
- 브라우저별 SDP 실패율을 모니터링한다.
추가 확인에 필요한 로그
error.name,error.stackanswer.typeanswer.sdp.startsWith("v=")answer.sdp.lengthanswer.sdp.slice(0, 300)- 브라우저 user agent
/api/realtime/call서버 로그의 OpenAI 응답 status와 SDP 길이
최종 판단
현재 로그 전체 흐름에서는 AI 세션 시작 중 WebRTC remote SDP 적용 실패가 가장 타당한 원인이다. instruction 문자열 문제, 일반 네트워크 문제, setSinkId 사용자 제스처 문제는 직접 원인으로 보기 어렵다.
다음 재현 시에는 SDP answer와 error metadata를 남기도록 로그를 보강해야 원인을 확정할 수 있다.